require 'active_record'

module ArtOfRails
  module Rdf #:nodoc:
    module Acts #:nodoc: 
      
      def self.included(base)
        base.extend(ClassMethods)
      end
      
      # Class-level methods
      module ClassMethods
        # Provides a to_rdf function for model obejcts and arrays of rdf-enabled objects.
        # 
        # Options are:
        # * <tt>:class_uri</tt>  - specify the URI of the class represented by this model. In future versions
        #   the <tt>acts_as_rdf</tt> function will determine this for you based on your Rails project configuration,
        #   but for now it is a requied option. An example class URI would be http://www.yoursite.com/users
        # * <tt>:class_type</tt> - specify <tt>rdf:type</tt> that this model represents. An example might be
        #   <tt>foaf:person</tt>. Make sure that any namespace you use is included in the namespace argument.
        # * <tt>:mappings</tt> - a hash that overrides the RDF properties used for each field on the database. The keys
        #   in the hash are the fields on the model object and the values are the RDF property names that should be 
        #   used instead. For example - <tt>{ :first_name => "foaf:given_name" }</tt>
        # * <tt>:methods</tt> - any methods on the model object whose retun value should be serialized as the datavalued
        #   object of an RDF property. The keys of the hash are the method names to call and the values are the RDF 
        #   properties that lead to their return values. 
        # * <tt>:include</tt> - any ActiveRecord associations to include with the output. The value of this property can
        #   be a number of things:
        #   * An array of symbols representing the names of the assotiations. Each association is simply referenced
        #   by a property with the default namespace and the same name as the association. So photos would become
        #   <tt>default:photos</tt>
        #   * A hash table where the keys are the names of the associations to include in the RDF export and the values
        #   are a property hash containing any properties defined in this list that should should override the
        #   defined properties for that class, in addition to the following properties:
        #    * <tt>:map</tt> - The name of the property that should lead to this object. Example:
        #    <tt>{ :map => 'ns:address' }</tt>
        #    * <tt>:style</tt> - The manner in which this associated object should be displayed
        def acts_as_rdf(options = {})
          # TODO: Make default namespace http://this.domain/Class/                  
          class_uri = 'http://www.example.org/Object'
          class_name = self.name.downcase
          
          configuration = { 
            :class_uri => class_uri,
            :property_prefix => class_name + '_property',
            :property_baseuri => class_uri + '#',
            :instance_prefix => class_name + '_instance',
            :instance_baseuri => class_uri + '/'       
          }          

          configuration.update(options) if options.is_a?(Hash)
          @rdf_export_configuration = configuration
          
          ArtOfRails::Rdf::Namespaces.register(configuration[:property_prefix], configuration[:property_baseuri])
          ArtOfRails::Rdf::Namespaces.register(configuration[:instance_prefix], configuration[:instance_baseuri])
          
          class_eval <<-EOV
            include ArtOfRails::Rdf::Acts::InstanceMethods
            extend ArtOfRails::Rdf::Acts::SingletonMethods
          EOV
        end
        
        # Returns the RDF property that corresponds to a particular field on the model. 
        # Return value is either a String or a Hash with keys [:ns, :base_uri, :local_name].
        # If it can figure out how to ply your property URI out as a namespace prefix, 
        # base-uri, and local name then it will return the hash. This isn't for any 
        # technical reason other than to make the RDFXML pretty. If it can't figure out
        # how to separate the components of your property, then it will just return
        # a string representing the URI.
        def field_to_rdf_property(field)
          configuration = rdf_export_configuration

          # Case 0
          # No mapping was given for this field. Assign to the default namespace
          
          unless (configuration[:mappings] && configuration[:mappings][field.to_sym])
            return {
              :ns => :default,
              :base_uri => configuration[:default_namespace],
              :local_name => field
            }
          end

          # Case 1
          # There was a mapping given for this field. 
          
          name = configuration[:mappings][field.to_sym]
          
          # There are three possibilities for the mapping:
          # A) The mapping is a fully qualified URI ->  Return it as a string
          # B) The mapping is prefixed with ns:Something -> Split out the : and return
          # C) The mapping is not prefixed -> return with default prefix
          
          # Case 1-A: The mapping is to a fully-qualified URI
          return name if name =~ /^[a-zA-Z]+:\/\//
          
          #  Case 1-B: The mapping is prefixed with ns:Something          
          if name.include? ':'
            pieces = name.split ':'
            ns = pieces.shift
            local_name = pieces.join
            return {
              :ns => :"#{ns}",
              :base_uri => ArtOfRails::Rdf::Namespaces.fetch_uri(ns),
              :local_name => local_name              
            }
          end
          
          # Case 1-C: Fall-through: it must be a local name with no prefix
          return {
            :ns => :default,
            :base_uri => configuration[:default_namespace],
            :local_name => name
          }            
        end
        
        def rdf_export_configuration
          @rdf_export_configuration.dup
        end                         
      end
      
      # Singleton methods
      module SingletonMethods
        def eval_sparql
        end
      end
      
      # Instance Methods
      module InstanceMethods
        def to_rdf(options = {}, &block)
          export_options = self.class.rdf_export_configuration.dup
          export_options.update(options) if options.is_a?(Hash)          
          serializer = ArtOfRails::Rdf::ActiveRecord::RdfSerializer.new(self, options)
          block_given? ? serializer.to_s(&block) : serializer.to_s
        end
        
        def resource_uri
          "#{self.class.rdf_export_configuration[:class_uri]}/#{self.id}"
        end
      end      
      
      module Extensions
        module Array
          def to_rdf(options = {})
            raise "Not all elements respond to to_rdf" unless all? { |e| e.respond_to? :to_rdf }
            serializer = ArtOfRails::Rdf::ActiveRecord::RdfSerializer.new(self, options)
            block_given? ? serializer.to_s(&block) : serializer.to_s
          end
        end
      end
      
    end
  end
end

